package io.gitee.tziye.core.id;

import io.gitee.tziye.common.ValueResult;
import io.gitee.tziye.core.instance.DefaultRenaiClient;
import io.gitee.tziye.enums.TimePattern;
import io.gitee.tziye.jdbc.JdbcProxy;
import io.gitee.tziye.jdbc.ProcedureResult;
import io.vavr.Tuple2;
import org.apache.commons.lang3.StringUtils;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import static io.gitee.tziye.common.Constants.*;

public class DefaultIdOperator extends IdOperator {

    private static final int CACHE_SIZE = 50;
    private static final int STEP_TIMES = 100;

    private final Map<String, Tuple2<Long, Long>> cacheMap = new LRUCache(CACHE_SIZE);

    private final JdbcProxy jdbcProxy;

    public DefaultIdOperator(DefaultRenaiClient renaiClient) {
        super(renaiClient);
        jdbcProxy = renaiClient.getJdbcProxy();
    }

    @Override
    public long generate() {
        // 18位通用Id
        RenaiIdSpec idSpec = new RenaiIdSpec(DEFAULT, TimePattern.EPOCHM, 5);
        return generate(idSpec);
    }

    @Override
    public long generate(RenaiIdSpec renaiIdSpec) {
        ValueResult result = commonGenerate(renaiIdSpec);
        return renaiIdSpec.returnId(result);
    }

    @Override
    public String generateCode(RenaiIdSpec renaiIdSpec) {
        ValueResult result = commonGenerate(renaiIdSpec);
        return renaiIdSpec.returnCode(result);
    }

    private synchronized ValueResult commonGenerate(RenaiIdSpec renaiIdSpec) {
        String prefix = renaiIdSpec.getPrefix();
        String time = renaiIdSpec.time();
        int step = renaiIdSpec.step();
        assert StringUtils.isNotBlank(prefix) && StringUtils.isNotBlank(time) && step > 0;
        ValueResult result = new ValueResult();
        String cacheKey = prefix + DELIMITER + time;
        Tuple2<Long, Long> cacheStep = cacheMap.get(cacheKey);
        if (cacheStep != null && cacheStep._1 + step <= cacheStep._2) {
            result.setLv(cacheStep._1 + step);
            result.setSv(time);
            cacheMap.put(cacheKey, new Tuple2<>(cacheStep._1 + step, cacheStep._2));
            return result;
        }
        int bigStep = step * STEP_TIMES;
        result = doGenerate(prefix, time, bigStep);
        long real = result.getLv() - bigStep + step;
        cacheMap.put(cacheKey, new Tuple2<>(real, result.getLv()));
        result.setLv(real);
        return result;
    }

    private ValueResult doGenerate(String prefix, String pattern, int step) {
        Map<String, Object> params = new HashMap<>();
        params.put(PREFIX, prefix);
        params.put(PATTERN, pattern);
        params.put(STEP, step);
        ProcedureResult result = jdbcProxy.call(ID_GENERATE, params);
        return result.get(ValueResult.class);
    }

    @Override
    public int delete(String prefix) {
        assert StringUtils.isNotBlank(prefix);
        String sql = "DELETE FROM renai_id_generator WHERE prefix = ?";
        return jdbcProxy.update(sql, prefix);
    }

    public static class LRUCache extends LinkedHashMap<String, Tuple2<Long, Long>> {
        int capacity;

        public LRUCache(int capacity) {
            super(capacity, 0.75F, true);
            this.capacity = capacity;
        }

        @Override
        protected boolean removeEldestEntry(Map.Entry<String, Tuple2<Long, Long>> eldest) {
            return size() > capacity;
        }
    }
}
